﻿// <copyright file="AppNotificationAsyncResult.cs" company="Robert Venables">
// Copyright (c) 2009 Robert Venables.
// </copyright>
// <author>Robert Venables</author>
// <email>rob@rvenables.com</email>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace VNotify
{
    /// <summary>
    /// APM AsyncResult implementation.
    /// </summary>
    internal class AppNotificationAsyncResult : IAsyncResult
    {
        /// <summary>
        /// A reference to a callback to be fired when the
        /// operation has completed.
        /// </summary>
        private readonly AsyncCallback asyncCallbackRef;

        /// <summary>
        /// A reference to an object associated with the operation.
        /// </summary>
        private readonly object _asyncState;

        #region "(Integer) State Representations"

            /// <summary>
            /// The integer representation of the pending (not finished) state.
            /// </summary>
            private const int StatePending = 0;

            /// <summary>
            /// The integer representation of the completed (synchronously) state.
            /// </summary>
            private const int StateCompletedSynchronously = 1;

            /// <summary>
            /// The integer representation of the completed (asynchronously) state.
            /// </summary>
            private const int StateCompletedAsynchronously = 2;

        #endregion

        /// <summary>
        /// Records how the operation was completed  (updated when 
        /// the operation finishes).
        /// </summary>
        private int completedState = StatePending;

        /// <summary>
        /// The reset event used to track when the operation has been finished.
        /// </summary>
        private ManualResetEvent completedSignal;

        /// <summary>
        /// Records any exceptions that occur during the operation (and throws
        /// them during the call to EndInvoke).
        /// </summary>
        private Exception failException;

        /// <summary>
        /// Initializes a new instance of the AppNotificationAsyncResult class
        /// with the specified callback and associated state.
        /// </summary>
        /// <param name="callback">Optional callback to fire when operation is completed.</param>
        /// <param name="state">Optional object state to associate with the operation.</param>
        public AppNotificationAsyncResult(AsyncCallback callback, object state)
        {
            this.asyncCallbackRef = callback;
            this._asyncState = state;
        }

        #region IAsyncResult Members

        /// <summary>
        /// Gets any object data that has been manually associated with the operation.
        /// </summary>
        public object AsyncState
        {
            get { return this._asyncState; }
        }

        /// <summary>
        /// Gets the WaitHandle associated with the operation.
        /// </summary>
        public WaitHandle AsyncWaitHandle
        {
            // (Note: Accessor not entirely thread-safe.)
            get 
            {
                if (this.completedSignal == null)
                {
                    // Likely no ManualResetEvent - create one and then
                    // check again (another thread could be creating one now)
                    bool done = this.IsCompleted;
                    ManualResetEvent resetEvent = new ManualResetEvent(done);

                    if (Interlocked.CompareExchange(
                        ref this.completedSignal,
                        resetEvent, 
                        null) != null)
                    {
                        /* We created one but so did another thread.
                         * CompareExchange cought this and did not update
                         * CompletedSignal with our new ResetEvent.
                         */ 

                        // Too slow, ResetEvent!
                        resetEvent.Close();
                    }
                    else
                    {
                        if (!done && this.IsCompleted)
                        {
                            this.completedSignal.Set();
                        }
                    }
                }

                return this.completedSignal;
            }
        }

        /// <summary>
        /// Gets a value indicating whether the operation was completed synchronously.
        /// </summary>
        public bool CompletedSynchronously
        {
            get
            {
                /* Volatile read is used because this property could be accessed
                 * by a separate thread than one calling SetAsCompleted() - resulting
                 * in a potential outdated value for CompletedState being read.
                 */
 
                return Thread.VolatileRead(ref this.completedState)
                        == StateCompletedSynchronously;
            }
        }

        /// <summary>
        /// Gets a value indicating whether the operation has completed yet.
        /// </summary>
        public bool IsCompleted
        {
            get
            {
                return
                    Thread.VolatileRead(ref this.completedState)
                        != StatePending;
            }
        }

        /// <summary>
        /// Blocks until the operation is complete.
        /// </summary>
        public void EndInvoke()
        {
            if (!this.IsCompleted)
            {
                this.AsyncWaitHandle.WaitOne();
                this.AsyncWaitHandle.Close();
                this.completedSignal = null;
            }

            // (Operation Finished)
            if (this.failException != null) throw this.failException;
        }

        #endregion

        /// <summary>
        /// Tells this AsyncResult that the send notification work has finished.
        /// </summary>
        /// <param name="failException">Exception encountered while sending notification (if encountered)</param>
        /// <param name="completedSynchronously">True if the operation was actually performed Synchronously.</param>
        public void RegisterCompleted(
            Exception failException,
            bool completedSynchronously)
        {
            this.failException = failException;

            /* Thread-safe update of CompletedState based on _CompletedSynchronously
             * argument. Updates PreviousState to old CompletedState value.
             */
 
            int previousState = Interlocked.Exchange(
                ref this.completedState,
                completedSynchronously ? StateCompletedSynchronously : StateCompletedAsynchronously);

            // Make sure that this method hasn't been called before
            if (previousState != StatePending)
                throw new InvalidOperationException(
                    "SetAsCompleted() Already Called! Can only be called once.");

            if (this.asyncCallbackRef != null) this.asyncCallbackRef(this);
            if (this.completedSignal != null) this.completedSignal.Set();
        }
    }
}